package edu.pku.sei.mdabase.infrastructure.model;

import java.util.Collection;
import java.util.List;

import edu.pku.sei.mdabase.basictypes.OCLVoidValue;
import edu.pku.sei.mdabase.core.IDataType;
import edu.pku.sei.mdabase.core.INamedElement;
import edu.pku.sei.mdabase.core.ITypedElement;
import edu.pku.sei.mdabase.infrastructure.Element;
import edu.pku.sei.mdabase.infrastructure.collection.OclCollection;
import edu.pku.sei.mdabase.infrastructure.event.MTEvent;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaReference;


public class MAttribute extends Element implements INamedElement, ITypedElement{
	private String name;
	private IDataType type;
	private Object value;
	
	/**
	 * @deprecated
	 * @return
	 */
	private boolean locked;
	
	private IAttributedElement host;

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public IDataType getType() {
		return type;
	}

	public void setType(IDataType type) {
		this.type = type;
	}

	public Object getValue() {
		return value;
	}

	public void setValue(Object value){
		Object oldValue = this.value;
		basicSetValue(value);
		if((OCLVoidValue.isNull(oldValue)&&!OCLVoidValue.isNull(value))||(!OCLVoidValue.isNull(oldValue)&&!oldValue.equals(value))){
			this.sendEvent(this, MTEvent.PROPERTY_CHANGE, oldValue,value);
		}
	}
	
	public void basicSetValue(Object value){
		if(value==null)
			value = OCLVoidValue.NULL_VALUE;
		this.value = value;
	}

	public IAttributedElement getHost() {
		return host;
	}

	protected void setHost(IAttributedElement host) {
		this.host = host;
	}
	
	public boolean equals(Object value){
		if(this.value!=null&&this.value.equals(value))
				return true;
		else return super.equals(value);
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append(name);
		builder.append('@');
		builder.append(type);
		builder.append('=');
		builder.append(value);
		return builder.toString();
	}

	/**
	 * @deprecated
	 * @return
	 */
	public boolean isLocked() {
		return locked;
	}

	/**
	 * @deprecated
	 * @return
	 */
	public void setLocked(boolean locked) {
		this.locked = locked;
	}
	
	static public MAttribute createMReferenceAttribute(MClass host, MetaReference type,Object key) {
		MReferenceAttribute attr = new MReferenceAttribute();
		attr.setHost(host);
		attr.setKey(key);
		attr.setType(type);
		attr.setName(type.getName());
		attr.basicSetValue(OCLVoidValue.VOID);
		
		return attr;
	}
}


class MReferenceAttribute extends MAttribute {
	private Object key;
	private MReference reference = null;
	
	
	public Object getKey() {
		return key;
	}

	public void setKey(Object key) {
		this.key = key;
	}


	public boolean isLocked(){
		return !isChangeable();
	}
	
	
	public void setType(IDataType type){
		super.setType(type);
		MetaReference t = (MetaReference)getType();
		
		
		setChangeable(t.isOppositeComposite()==false);
	}
	public Object getValue(){
		updateValue();
		return super.getValue();
	}
	
	
	public void setValue(Object value){
		Object obj = getValue();
		
		
		MetaReference type = (MetaReference)getType();
		MClass host = (MClass)this.getHost();
		
		if(OCLVoidValue.isNull(obj)){
			if(OCLVoidValue.isNull(value)==false){
				MReference ref = new MReference();
				MModelGraph model = (MModelGraph)host.getHost(getKey());
				model.addNode((MClass)value);
				ref.setType(type);
				ref.setSource(host);
				ref.setTarget((MClass)value);
				model.addEdge(ref);
			}
		} else {
			if(obj instanceof MClass){//type.isMulitple()==false
				assert !type.isOppositeComposite();
				
				//delete the old link
				host.getHost(getKey()).removeEdge(reference);
				
				//create new link
				if(OCLVoidValue.isNull(value)==false){
					if(value instanceof MClass){
						MReference ref = new MReference();						
						MModelGraph model = (MModelGraph)host.getHost(getKey());						
						model.addNode((MClass)value);						
						ref.setType(type);	
						ref.setSource(host);
						ref.setTarget((MClass)value);
						model.addEdge(ref);
					} else if(value instanceof Collection){
						if(((Collection)value).size()==0)
							value = OCLVoidValue.NULL_VALUE;
						else{
							value = ((Collection)value).iterator().next();
						}
					} else {
						System.out.println("unable to set a collection");
					}
				}
				basicSetValue(value);
			} else if(obj instanceof OclCollection){
				assert false;
			}
		}
	}
	
	public void updateValue(){
		
		
		MetaReference type = (MetaReference)getType();
		MClass host = (MClass)this.getHost();
		
		boolean flag = host.isEdgeChanged(key)==false;
		
		
		List<MReference> refs = host.getEdges(key);
		if(type.isMultiple()==false) {
			if(flag&&super.getValue()!=OCLVoidValue.VOID) return;
			
			for (MReference r : refs) {
				if(r.getType().isA(getType())){
					MClass end = r.getEnd(host,getName());
					if(end!=null) {
						reference = r;
					}
					basicSetValue(end);
				}
			}
		} else {
			OclCollection col = null;
			MetaReference rt = (MetaReference) getType();

			if(super.getValue() instanceof OclCollection){
				col = (OclCollection)super.getValue();
			}else 
				col = OclCollection.createReferenceCollection(host,rt,key);
			
			if(!flag){
				OclCollection.resetCollection(col);
				
				for (MReference r : refs) {
					if(r.getType().isA(rt)){
						MClass end = r.getEnd(host,getName());
						if(end!=null)
							OclCollection.simpleAdd(col, end);
					}
				}
			}
			basicSetValue(col);
		}
	}
}
